#include <algorithm>
#include "rs_regex.h"


regex_charset lowercase_ascii_letters_charset (L"abcdefghijklmnopqrstuwvxyz");
regex_charset cmudict_charset (L"abcdefghijklmnopqrstuwvxyz_012");


bool rs_regex::parse ()
{
    // parses rsr_str like L"[^aeiou][aeiou]pping" or L"^a.e$"
    // into rsr_vec, rsr_e_fix and rsr_s_fix
    size_t i=0, j;
    rs_string pattern (rsr_str);
    strip_regex_start_end (pattern, rsr_s_fix, rsr_e_fix);
    size_t len=pattern.length();
    rsr_vec.clear();
    unsigned allowed_size = rsr_charset->size();
    while (i<len) {
        vector<bool> allowed (allowed_size, false);
        unsigned pos = rsr_charset->pos (pattern[i]);
        if (pos < allowed_size) {
            allowed[pos] = true;
            i++;
        } else if (pattern[i] == L'.') {
            // any character matches
            for (j=0; j<allowed_size; j++)
                allowed[j] = true;
            i++;
        } else if (pattern[i] == L'[') {
            j = pattern.find (L']', i);
            if (j == string::npos) {
                rs_report_error (L"parsing regular expression \'" +
                    pattern + L"', no matching ']' for '[' at position " + rs_stringed (i));
                return false;
            }
            bool negate  = pattern[++i] == L'^';
            if (negate)
                i++;
            while (i<j) {
                pos = rsr_charset->pos (pattern[i]);
                if (pos >= allowed_size) {
                    rs_report_error (L"parsing regular expression \'" +
                        pattern + L"', incorrect character within [] at position " + rs_stringed (i));
                    return false;
                }
                allowed[pos] = true;
                i++;
            }
            if (negate) {
                for (unsigned k=0; k<allowed_size; k++)
                    allowed[k] = !allowed[k];
            }
            i++;
        } else {
            rs_report_error (L"parsing regular expression \'" +
                pattern + L"', unknown character at position " + rs_stringed (i));
            return false;
        }
        rsr_vec.push_back (allowed);
    }
    return true;
}


bool rs_regex::base_match (const rs_string& re_str) const
{
    // checks whether full string re_str matches
    if (!rsr_valid)
        return false;
    if (re_str.size() != rsr_vec.size())
        return false;
    size_t i;
    size_t len=re_str.length();
    unsigned allowed_size = rsr_charset->size();
    for(i=0; i<len; i++) {
        unsigned pos = rsr_charset->pos (re_str[i]);
        if (pos >= allowed_size)
            return false;
        if (!rsr_vec[i][pos])
            return false;
    }
    return true;
}


rs_string rs_regex::stringed_re () const
{
    // converts a regular expression to its string form
    rs_string result;
    size_t i, j, len = rsr_vec.size();
    if (rsr_s_fix)
        result = L"^";
    for (i=0; i<len; i++) {
        rs_string set0, set1;
        for (j=0; j<rsr_charset->size(); j++) {
            if (rsr_vec[i][j])
                set1 += rsr_charset->get_char (j); // present characters
            else
                set0 += rsr_charset->get_char (j); // absent characters
        }
        if (set1.length() == 1)
            result += set1;
        else if (set0.length() == 0)
            result += L'.';
        else if (set1.length() <= set0.length())
            result += L'['+set1+L']';
        else
            result += L"[^"+set0+L']';
    }
    if (rsr_e_fix)
        result += L"$";
    return result;
}


bool rs_regex::matches (const rs_string& str) const
{
    if (str.length() < rsr_vec.size())
        return false;
    bool result;
    if (rsr_s_fix && rsr_e_fix)
        result = base_match (str);
    else if (rsr_e_fix)
        result = base_match (str.substr (str.length() - rsr_vec.size()));
    else
        result = base_match (str.substr (0, rsr_vec.size()));
    return result;
}
